自定义高仿微信聊天内容组件
概述
本节实现一个高仿微信聊天消息的 MessageItem 组件,包含时间显示逻辑、头像布局(左右切换)和消息主体内容区域,采用 Flex 布局实现紧凑的聊天界面。
组件需求分析
微信聊天消息组件的核心结构:
┌─────────────────────────────┐
│ 时间戳(条件显示) │
├─────────────────────────────┤
│ [头像] 消息内容 (对方) │
│ 消息内容 [头像] (自己) │
└─────────────────────────────┘
text
| 功能模块 | 说明 |
|---|---|
| 时间戳 | 按时间间隔条件显示,非每条消息都展示 |
| 头像 | 支持左右切换(自己右侧,对方左侧) |
| 消息内容 | 文本/图片/系统消息等类型 |
| 布局 | Flex 居顶对齐,紧凑排列 |
组件实现
Props 类型定义
// types.ts
import type { AvatarProps } from 'element-plus'
export interface MessageItemProps {
/** 消息内容 */
content: string
/** 消息类型 */
type?: 'text' | 'image' | 'system'
/** 是否为自己发送的消息 */
isSelf?: boolean
/** 消息创建时间 */
createdAt?: string | Date
/** 头像配置 */
avatarProps?: Partial<AvatarProps>
/** 是否显示时间戳 */
showTime?: boolean
}
typescript
MessageItem 组件
<template>
<div class="message-item">
<!-- 时间戳 -->
<div v-if="showTime" class="message-time">
{{ formatTime(createdAt) }}
</div>
<!-- 消息主体 -->
<div
class="message-body"
:class="{ 'is-self': isSelf }"
>
<!-- 头像 -->
<el-avatar
v-bind="avatarProps"
:class="['message-avatar', isSelf ? 'avatar-right' : 'avatar-left']"
/>
<!-- 消息内容 -->
<div class="message-content">
<div class="content-bubble">
<span>{{ content }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { MessageItemProps } from './types'
import { ElAvatar } from 'element-plus'
import type { AvatarProps } from 'element-plus'
const props = withDefaults(defineProps<MessageItemProps>(), {
type: 'text',
isSelf: false,
showTime: false,
avatarProps: () => ({
shape: 'square',
size: 'default',
src: ''
})
})
function formatTime(time: string | Date | undefined): string {
if (!time) return ''
const date = new Date(time)
const now = new Date()
const isToday = date.toDateString() === now.toDateString()
if (isToday) {
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})
}
return date.toLocaleDateString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
</script>
<style scoped>
.message-item {
padding: 4px 16px;
}
.message-time {
text-align: center;
font-size: 12px;
color: #999;
margin: 8px 0;
}
.message-body {
display: flex;
align-items: flex-start;
gap: 8px;
}
.message-body.is-self {
flex-direction: row-reverse;
}
.message-avatar {
flex-shrink: 0;
}
.message-content {
max-width: 70%;
}
.content-bubble {
padding: 10px 14px;
border-radius: 8px;
background-color: #fff;
word-break: break-word;
line-height: 1.5;
}
.is-self .content-bubble {
background-color: #95ec69;
}
</style>
vue
使用示例
<template>
<div class="chat-container">
<MessageItem
v-for="msg in messages"
:key="msg.id"
:content="msg.content"
:is-self="msg.isSelf"
:created-at="msg.createdAt"
:show-time="shouldShowTime(msg, messages)"
:avatar-props="{ src: msg.avatar }"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import MessageItem from './MessageItem.vue'
interface ChatMessage {
id: number
content: string
isSelf: boolean
createdAt: string
avatar: string
}
const messages = ref<ChatMessage[]>([
{
id: 1,
content: '你好,请问项目进度如何?',
isSelf: false,
createdAt: '2026-05-19T10:00:00',
avatar: ''
},
{
id: 2,
content: '组件库已经完成 90%,正在做最后的测试',
isSelf: true,
createdAt: '2026-05-19T10:01:00',
avatar: ''
}
])
// 时间间隔超过 5 分钟才显示时间戳
function shouldShowTime(msg: ChatMessage, list: ChatMessage[]): boolean {
const index = list.findIndex(m => m.id === msg.id)
if (index === 0) return true
const prev = new Date(list[index - 1].createdAt).getTime()
const curr = new Date(msg.createdAt).getTime()
return curr - prev > 5 * 60 * 1000 // 5分钟
}
</script>
vue
扩展方向
- 消息类型扩展:支持图片消息、语音消息、系统消息等
- 头像 Props 优化:使用
Partial<AvatarProps>确保类型安全 - 长按菜单:复制、转发、撤回等操作
- 消息气泡箭头:CSS 三角形指示方向
- 滚动优化:新消息自动滚动到底部,避免输入时跳动
↑